package com.affectu.dao.pagination;

import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.context.FacesContext;

import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.Range;
import org.ajax4jsf.model.SequenceRange;
import org.apache.commons.beanutils.PropertyUtils;

public abstract class BaseDataModel<T, ID extends Serializable> extends ExtendedDataModel {

    public List<T> listRow = null;

    private int firstRow_old = 0;

    private ID currentId;

    private Map<ID, T> wrappedData = new HashMap<ID, T>();

    private Long rowCount; // better to buffer row count locally

    public abstract Long getCount();

    public abstract List<T> getList(Integer firstRow, Integer maxResults);

    public abstract T findById(ID id);

    @SuppressWarnings("unchecked")
    public void wrap(FacesContext context, DataVisitor visitor, Range range, Object argument, List<T> list) throws IOException {
        wrappedData = new HashMap<ID, T>();
        for (T row : list) {
            ID id = null;
            try {
                id = (ID) PropertyUtils.getProperty(row, "id");
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
            // wrappedKeys.add(id);
            wrappedData.put(id, row);
            visitor.process(context, id, argument);
        }
    }

    public boolean hasById(ID id) {
        return wrappedData.get(id) != null;
    }

    @Override
    public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument) throws IOException {
        int firstRow = ((SequenceRange) range).getFirstRow();
        int maxResults = ((SequenceRange) range).getRows();
        if (firstRow != firstRow_old || listRow == null) {
            listRow = getList(firstRow, maxResults);
            firstRow_old = firstRow;
        }
        wrap(context, visitor, range, argument, listRow);
    }

    /*
     * This method normally called by Visitor before request Data Row.
     */
    @SuppressWarnings("unchecked")
    @Override
    public void setRowKey(Object key) {
        this.currentId = (ID) key;
    }

    @Override
    public int getRowCount() {
        if (rowCount == null)
            return (rowCount = this.getCount()).intValue();
        else
            return rowCount.intValue();
    }

    @Override
    public boolean isRowAvailable() {
        if (currentId == null) {
            return false;
        } else {
            return hasById(currentId);
        }
    }

    /**
     * This is main way to obtain data row. It is intensively used by framework. We strongly recommend use of local
     * cache in that method.
     */
    @Override
    public Object getRowData() {
        if (currentId == null) {
            return null;
        } else {
            T ret = wrappedData.get(currentId);
            if (ret == null) {
                ret = this.findById(currentId);
                wrappedData.put(currentId, ret);
                return ret;
            } else {
                return ret;
            }
        }
    }

    /**
     * Unused rudiment from old JSF staff.
     */
    @Override
    public Object getWrappedData() {
        throw new UnsupportedOperationException();
    }

    private int rowIndex = 0;

    @Override
    public int getRowIndex() {
        // throw new UnsupportedOperationException();
        return rowIndex;
    }

    @Override
    public void setRowIndex(int rowIndex) {
        // throw new UnsupportedOperationException();
        this.rowIndex = rowIndex;
    }

    @Override
    public void setWrappedData(Object data) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Object getRowKey() {
        if (true)
            throw new UnsupportedOperationException();
        return currentId;
    }
}
